/*
   ARDUINO BASED MULTICOLOR SOUND PLAYING DARKSABER!  
   By 大玉菌       
   硬件:
     LED strip (WS2812)    
     MicroSD card module    
     IMU MPU6050 (accel + gyro)  陀螺仪
     HXJ8002    音频模块
     XR2981      mini升压模块
     arduino nano v3
   功能:
     光剑音效    
      swing mode：                                                                   
        Slow swing - long hum sound (randomly from 4 sounds)      //慢晃                     
        Fast swing - short hum sound (randomly from 5 sounds)     //快晃
      HIT mode：      
        Weak hit - short sound
        Hard hit - long "bzzzghghhdh" sound
     电池安全模式:
       电量低时，开机按钮灯会闪烁
       电量低时，自动关机
   按键:                                                                     
     长按开关
     改变颜色
     颜色模式存储在EEPROM    
*/

// ---------------------------- 参数 -------------------------------
#define NUM_LEDS 66         // 灯带数量
#define BTN_TIMEOUT 800     // 按键刷新时间
#define BRIGHTNESS 255      // 灯带亮度

#define SWING_TIMEOUT 500   // 摆动刷新时间
#define SWING_L_THR 150     // swing 角度阈值
#define SWING_THR 300       // fast swing 角度阈值
#define STRIKE_THR 150      // hit 加速度阈值    
#define STRIKE_S_THR 320    // hard hit 加速度阈值 
#define FLASH_DELAY 80      // 敲击延时

#define PULSE_ALLOW 1       //脉冲使能
#define PULSE_AMPL 20       //脉冲幅度      
#define PULSE_DELAY 30      //脉冲间隔

#define R1 51000            //分压电阻 
#define R2 51000            //分压电阻
#define BATTERY_SAFE 1      //电池监测(1 - allow, 0 - disallow)             

#define DEBUG 0             //调试模式(1 - allow, 0 - disallow)   

#define HUM_TIMEOUT    9000     //HUM.wav 刷新时间


// ---------------------------- PIN -------------------------------
#define LED_PIN 6           //灯带PIN                                
#define BTN 3               //按键PIN
#define IMU_GND A1          //MPU6050 GND      
#define SD_GND A0           //MICRO SD GND
#define VOLT_PIN A6         //电压检测 PIN
#define BTN_LED 4           //指示灯 PIN

// -------------------------- LIBS ---------------------------
#include <avr/pgmspace.h>   // PROGMEM library  把数据存储到flash里面
#include <SD.h>
#include <TMRpcm.h>         // audio from SD library
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "FastLED.h"        // addressable LED library
#include <EEPROM.h>

// ------------------------------ VARIABLES ---------------------------------

CRGB leds[NUM_LEDS];
#define LED_TYPE WS2812         // LED灯带型号
#define COLOR_ORDER GRB         // RGB灯珠中红色、绿色、蓝色LED的排列顺序

#define SD_ChipSelectPin 8    //Micro SD CS
TMRpcm tmrpcm;   

MPU6050 accelgyro;
int16_t ax, ay, az;
int16_t gx, gy, gz;
unsigned long ACC, GYR, COMPL;
int gyroX, gyroY, gyroZ, accelX, accelY, accelZ, freq, freq_f = 20;

float k = 0.2;
unsigned long humTimer = -HUM_TIMEOUT, mpuTimer, nowTimer;   //上次时间计数
int stopTimer;
boolean bzzz_flag, ls_chg_state, ls_state;  //bzzz_flag:喇叭状态 ls_chg_state:切换状态   ls_state:开关状态 
boolean btnState, btn_flag, hold_flag;      //btnState:按键状态  btn_flag：按键标志 hold_flag：按住标志
byte btn_counter;                           //按键计数器
unsigned long btn_timer, PULSE_timer, swing_timer, swing_timeout, battery_timer, bzzTimer;   //btn_timer：按键更新时间 PULSE_timer：脉冲更新时间 swing_timer：摆动更新时间 swing_timeout：摆动更新 battery_timer：电池更新时间  bzzTimer：音调更新时间
byte nowNumber;     //当前音频          
byte nowColor = 0, red, green, blue, redOffset, greenOffset, blueOffset;   //nowColor 当前颜色  
boolean eeprom_flag, swing_flag, swing_allow, strike_flag;    //eeprom_flag:e2p存储标志位 swing_flag：晃动标志位 swing_allow：未使用 strike_flag：敲击标志位 
float voltage;                                                         //电压
int PULSEOffset;
// ------------------------------ VARIABLES ---------------------------------

// --------------------------------- SOUNDS ----------------------------------
//Hard hit
const char strike1[] PROGMEM = "SK1.wav";
const char strike2[] PROGMEM = "SK2.wav";
const char strike3[] PROGMEM = "SK3.wav";
const char strike4[] PROGMEM = "SK4.wav";
const char strike5[] PROGMEM = "SK5.wav";
const char strike6[] PROGMEM = "SK6.wav";
const char strike7[] PROGMEM = "SK7.wav";
const char strike8[] PROGMEM = "SK8.wav";

const char* const strikes[] PROGMEM  = {
  strike1, strike2, strike3, strike4, strike5, strike6, strike7, strike8
};

int strike_time[8] = {779, 563, 687, 702, 673, 661, 666, 635};

// Weak hit
const char strike_s1[] PROGMEM = "SKS1.wav";
const char strike_s2[] PROGMEM = "SKS2.wav";
const char strike_s3[] PROGMEM = "SKS3.wav";
const char strike_s4[] PROGMEM = "SKS4.wav";
const char strike_s5[] PROGMEM = "SKS5.wav";
const char strike_s6[] PROGMEM = "SKS6.wav";
const char strike_s7[] PROGMEM = "SKS7.wav";
const char strike_s8[] PROGMEM = "SKS8.wav";

const char* const strikes_short[] PROGMEM = {
  strike_s1, strike_s2, strike_s3, strike_s4,
  strike_s5, strike_s6, strike_s7, strike_s8
};
int strike_s_time[8] = {270, 167, 186, 250, 252, 255, 250, 238};

//Slow swing
const char swing1[] PROGMEM = "SWS1.wav";
const char swing2[] PROGMEM = "SWS2.wav";
const char swing3[] PROGMEM = "SWS3.wav";
const char swing4[] PROGMEM = "SWS4.wav";
const char swing5[] PROGMEM = "SWS5.wav";

const char* const swings[] PROGMEM  = {
  swing1, swing2, swing3, swing4, swing5
};
int swing_time[8] = {389, 372, 360, 366, 337};
 
//Fast swing 
const char swingL1[] PROGMEM = "SWL1.wav";    
const char swingL2[] PROGMEM = "SWL2.wav";
const char swingL3[] PROGMEM = "SWL3.wav";
const char swingL4[] PROGMEM = "SWL4.wav";

const char* const swings_L[] PROGMEM  = {
  swingL1, swingL2, swingL3, swingL4
};
int swing_time_L[8] = {636, 441, 772, 702};

char BUFFER[10];
// --------------------------------- SOUNDS ---------------------------------

void setup() {
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);  // 初始化光带  
  FastLED.setBrightness(100);  // ~40% of LED strip brightness
  setAll(0, 0, 0);             // and turn it off

  Wire.begin();                        
  Serial.begin(9600);

  // ---- 按键初始化 ----
  pinMode(BTN, INPUT_PULLUP);
  pinMode(IMU_GND, OUTPUT);
  pinMode(SD_GND, OUTPUT);
  pinMode(BTN_LED, OUTPUT);
  digitalWrite(IMU_GND, 0);
  digitalWrite(SD_GND, 0);
  digitalWrite(BTN_LED, 1);
  // ---- 按键初始化 ----

  randomSeed(analogRead(2));    // starting point for random generator   
  delay(200);
  // IMU 6050 initialization
  accelgyro.initialize();
  accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_16);
  accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250);   
  if (DEBUG) {
    if (accelgyro.testConnection()) Serial.println(F("MPU6050 OK"));
    else Serial.println(F("MPU6050 fail"));
  }

  // SD initialization
  // Pin MOSI ===> D11   ArduinoPin MISO ===> D12    ArduinoPin SCK ===> D13    ArduinoPin CS ===>D8
  tmrpcm.speakerPin = 9;    //定义扬声器引脚  5,6,11 or 46 on Mega, 9 on Uno, Nano, etc 
  tmrpcm.setVolume(5);      //音量  
  tmrpcm.quality(1);
  if (DEBUG) {
    if (SD.begin(SD_ChipSelectPin)) Serial.println(F("SD OK"));
    else Serial.println(F("SD fail"));
  } else {
    SD.begin(SD_ChipSelectPin);
  } 
  if ((EEPROM.read(0) >= 0) && (EEPROM.read(0) <= 5)) {  // check first start
    nowColor = EEPROM.read(0);   // remember color
  } else {                       // first start
    EEPROM.write(0, 0);          // set default
    nowColor = 0;                // set default
  }
  setColor(nowColor);
  if (DEBUG) {
    Serial.println(nowColor);
  }   
  delay(1000);                         // 1 second to show battery level
  setAll(0, 0, 0);
  FastLED.setBrightness(BRIGHTNESS);   // set bright
  tmrpcm.disable();
}

// --- MAIN LOOP---
void loop() {
  randomPULSE();
  getFreq();
  on_off_sound();
  btnTick();
  strikeTick();   //hit mode
  swingTick();    //swing mode
  batteryTick();
}
// --- MAIN LOOP---

//按键状态函数
void btnTick() {
  btnState = !digitalRead(BTN);                        //获取按键状态
  if (btnState && !btn_flag)                           //按下                                           
  {                                  
    if (DEBUG) Serial.println(F("BTN PRESS"));
    btn_flag = 1;                                      //按下的状态
    btn_counter++;                                      
    btn_timer = millis();                              //按下的时间
  }
  if (!btnState && btn_flag) {                         //按键释放
    btn_flag = 0;
    hold_flag = 0;                                                                 
  }
  //长按 开关光剑
  if (btn_flag && btnState && (millis() - btn_timer > BTN_TIMEOUT) && !hold_flag) 
  {
    ls_chg_state = 1;                     // flag to change saber state (on/off)
    hold_flag = 1;                        
  }
  //连按 触发
  if ((millis() - btn_timer > BTN_TIMEOUT) && (btn_counter != 0)) {
    if (ls_state) {
      if (btn_counter >= 3) {               // 3 press count
        nowColor++;                         // change color
        if (nowColor >= 6) nowColor = 0;
        setColor(nowColor);
        setAll(red, green, blue);
        eeprom_flag = 1;
      }
    }
    btn_counter = 0;
  }
}


//声音开关 切换完执行一次
void on_off_sound() {
  if (ls_chg_state) {                // if change flag
    if (!ls_state)                   // if Saber is turned off   关的时候 
    {
      if (voltage_measure() > 10 || !BATTERY_SAFE) 
      {
        if (DEBUG) Serial.println(F("SABER ON"));
        tmrpcm.play("ON.wav");       //播放SD卡 ON.wav                               
        delay(200);                       
        light_up();                  //灯亮 
        delay(200);
        bzzz_flag = 1;
        ls_state = true;             // remember that turned on
        tmrpcm.play("HUM.wav");      //播放sd卡 HUM.wav
      } 
      else 
      {
        if (DEBUG) Serial.println(F("LOW VOLTAGE!"));
        for (int i = 0; i < 5; i++) {
          digitalWrite(BTN_LED, 0);
          delay(400);
          digitalWrite(BTN_LED, 1);
          delay(400);
        }
      }
    } else {                         // turned on
      bzzz_flag = 0;
      tmrpcm.play("OFF.wav");         //播放OFF.wav
      delay(300);
      light_down();                   //关灯
      delay(300);
      tmrpcm.disable();               //sd卡播放关闭             
      if (DEBUG) Serial.println(F("SABER OFF"));
      ls_state = false;               
      if (eeprom_flag) {
        eeprom_flag = 0;
        EEPROM.write(0, nowColor);   // write color in EEPROM
      }
    }
    ls_chg_state = 0;
  }

  //SD卡音频播放
  if (((millis() - humTimer) > HUM_TIMEOUT) && bzzz_flag) {
    tmrpcm.play("HUM.wav");                //HUM时长为9000ms 所以humTimer设为-9000
    humTimer = millis();
    swing_flag = 1;
    strike_flag = 0;
  }
}
 
 //随机脉冲
void randomPULSE() {
  //PULSE_ALLOW 使能脉冲 ls_state 按键打开
  if (PULSE_ALLOW && ls_state && (millis() - PULSE_timer > PULSE_DELAY)) {
    PULSE_timer = millis();
    PULSEOffset = PULSEOffset * k + random(-PULSE_AMPL, PULSE_AMPL) * (1 - k);     //一阶滤波
    if (nowColor == 0) PULSEOffset = constrain(PULSEOffset, -15, 5);
    redOffset = constrain(red + PULSEOffset, 0, 255);
    greenOffset = constrain(green + PULSEOffset, 0, 255);
    blueOffset = constrain(blue + PULSEOffset, 0, 255);
    setAll(redOffset, greenOffset, blueOffset);
  }
}

//敲击动作
void strikeTick() {
  if ((ACC > STRIKE_THR) && (ACC < STRIKE_S_THR)) {     
    nowNumber = random(8);
    // читаем название трека из PROGMEM
    strcpy_P(BUFFER, (char*)pgm_read_word(&(strikes_short[nowNumber])));
    tmrpcm.play(BUFFER);
    hit_flash();                                                                            
    humTimer = millis() - HUM_TIMEOUT + strike_s_time[nowNumber] - FLASH_DELAY;    //-9000是因为HUM.wav是9000ms   
    strike_flag = 1;                                                          //敲击标志位
  }
  if (ACC >= STRIKE_S_THR) {
    nowNumber = random(8);
    // читаем название трека из PROGMEM
    strcpy_P(BUFFER, (char*)pgm_read_word(&(strikes[nowNumber])));
    tmrpcm.play(BUFFER);
    hit_flash();
    humTimer = millis() - HUM_TIMEOUT + strike_time[nowNumber] - FLASH_DELAY;
    strike_flag = 1;
  }
}

//摆动动作  
void swingTick() {
  if (GYR > 80 && (millis() - swing_timeout > 100))     //100ms更新一次
  {
    swing_timeout = millis();
    if (((millis() - swing_timer) > SWING_TIMEOUT) && swing_flag && !strike_flag) {
      if (GYR >= SWING_THR) {                             //swing               
        nowNumber = random(5);          
        // читаем название трека из PROGMEM
        strcpy_P(BUFFER, (char*)pgm_read_word(&(swings[nowNumber])));
        tmrpcm.play(BUFFER);               
        humTimer = millis() - HUM_TIMEOUT + swing_time[nowNumber];
        swing_flag = 0;
        swing_timer = millis();
        swing_allow = 0;
      }
      if ((GYR > SWING_L_THR) && (GYR < SWING_THR)) {     //fast swing
        nowNumber = random(5);            
        // читаем название трека из PROGMEM
        strcpy_P(BUFFER, (char*)pgm_read_word(&(swings_L[nowNumber])));
        tmrpcm.play(BUFFER);              
        humTimer = millis() - HUM_TIMEOUT + swing_time_L[nowNumber];
        swing_flag = 0;
        swing_timer = millis();
        swing_allow = 0;
      }
    }
  }
}

//获取陀螺仪数据
void getFreq() {
  if (ls_state) {                                               // if saber is on
    if (millis() - mpuTimer > 500) {                            
      accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);       

      // find absolute and divide on 100
      gyroX = abs(gx / 100);
      gyroY = abs(gy / 100);
      gyroZ = abs(gz / 100);
      accelX = abs(ax / 100);
      accelY = abs(ay / 100);
      accelZ = abs(az / 100);

      // vector sum
      ACC = sq((long)accelX) + sq((long)accelY) + sq((long)accelZ);
      ACC = sqrt(ACC);
      GYR = sq((long)gyroX) + sq((long)gyroY) + sq((long)gyroZ);
      GYR = sqrt((long)GYR);
      COMPL = ACC + GYR;
      /*
         // отладка работы IMU
         Serial.print("$");
         Serial.print(gyroX);
         Serial.print(" ");
         Serial.print(gyroY);
         Serial.print(" ");
         Serial.print(gyroZ);
         Serial.println(";");
      */
      freq = (long)COMPL * COMPL / 1500;                        // parabolic tone change
      freq = constrain(freq, 18, 300);                          
      freq_f = freq * k + freq_f * (1 - k);                     // smooth filter  一阶滤波
      mpuTimer = micros();                                      // 获取当前的微秒数                                
    }
  }
}

void setPixel(int Pixel, byte red, byte green, byte blue) {
  leds[Pixel].r = red;
  leds[Pixel].g = green;
  leds[Pixel].b = blue;
}

//灯带刷新
void setAll(byte red, byte green, byte blue) {
  for (int i = 0; i < NUM_LEDS; i++ ) {
    setPixel(i, red, green, blue);
  }
  FastLED.show();
}

//灯带打开
void light_up() {
  for (char i = 0; i <= (NUM_LEDS / 2 - 1); i++) {        
    setPixel(i, red, green, blue);
    setPixel((NUM_LEDS - 1 - i), red, green, blue);
    FastLED.show();
    delay(25);
  }
}

//灯带关闭
void light_down() {
  for (char i = (NUM_LEDS / 2 - 1); i >= 0; i--) {      
    setPixel(i, 0, 0, 0);
    setPixel((NUM_LEDS - 1 - i), 0, 0, 0);
    FastLED.show();
    delay(25);
  }
}

//敲击
void hit_flash() {
  setAll(255, 255, 255);            
  delay(FLASH_DELAY);                
  setAll(red, blue, green);        
}

//配置颜色
void setColor(byte color) {
  switch (color) {
    // 0 - red, 1 - green, 2 - blue, 3 - pink, 4 - yellow, 5 - ice blue
    case 0:
      red = 255;
      green = 0;
      blue = 0;
      break;
    case 1:
      red = 0;
      green = 0;
      blue = 255;
      break;
    case 2:
      red = 0;
      green = 255;
      blue = 0;
      break;
    case 3:
      red = 255;
      green = 0;
      blue = 255;
      break;
    case 4:
      red = 255;
      green = 255;
      blue = 0;
      break;
    case 5:
      red = 0;
      green = 255;
      blue = 255;
      break;
  }
}

//电池检测
void batteryTick() {
  if (millis() - battery_timer > 30000 && ls_state && BATTERY_SAFE) {
    if (voltage_measure() < 15) {
      ls_chg_state = 1;
    }
    battery_timer = millis();
  }
}

//获取电池电量
byte voltage_measure() {
  voltage = 0;
  for (int i = 0; i < 10; i++) {    
    voltage += (float)analogRead(VOLT_PIN) * 5.0 / 1023.0 * R2 / (R1 + R2) ;
  }
  voltage = voltage / 10;           
  int volts = voltage * 2 * 100;    
  if (volts > 387)
    return map(volts, 420, 387, 100, 77);
  else if ((volts <= 387) && (volts > 375) )
    return map(volts, 387, 375, 77, 54);
  else if ((volts <= 375) && (volts > 368) )
    return map(volts, 375, 368, 54, 31);
  else if ((volts <= 368) && (volts > 340) )
    return map(volts, 368, 340, 31, 8);
  else if (volts <= 340)
    return map(volts, 340, 260, 8, 0);
}
